/* * Copyright 2015 Philip Cronje * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package net.za.slyfox.dyn53; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.joran.JoranConfigurator; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; import ch.qos.logback.core.joran.spi.JoranException; import ch.qos.logback.core.util.StatusPrinter; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.Stage; import net.za.slyfox.dyn53.bean.Lifecycle; import net.za.slyfox.dyn53.extip.ExternalIpModule; import net.za.slyfox.dyn53.extip.StatefulUpdateModule; import net.za.slyfox.dyn53.extip.UnconditionalUpdateModule; import net.za.slyfox.dyn53.route53.Route53Module; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; import java.io.BufferedReader; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashSet; import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; /** * Application launcher class. */ public final class Dyn53 implements Runnable { private static final AtomicInteger shutdownThreadCounter = new AtomicInteger(1); private final Set<Lifecycle> lifecycleObjects; private final Logger logger = LoggerFactory.getLogger(getClass()); private final Runtime runtime; @Inject public Dyn53(Set<Lifecycle> lifecycleObjects, Runtime runtime) { this.lifecycleObjects = Objects.requireNonNull(lifecycleObjects); this.runtime = Objects.requireNonNull(runtime); } /** * Initialises dependency injection, obtains an instance of this class, and {@linkplain #run() runs} it. * * @param arguments array of command line arguments */ public static void main(String[] arguments) { /** * Load configuration properties from a file, if specified. If a file is specified, `properties` will refer to * a copy of System.getProperties(), with the configuration file properties as its default values fallback. * Otherwise, `properties` will be aliased to System.getProperties(). */ final String configurationFilePath = System.getProperty("net.za.slyfox.dyn53.configurationFile"); final Properties properties; if(configurationFilePath != null) { final Properties fileProperties = new Properties(); try(final BufferedReader reader = Files.newBufferedReader(Paths.get(configurationFilePath))) { fileProperties.load(reader); } catch(IOException e) { LoggerFactory.getLogger(Dyn53.class) .error("Could not load configuration properties from {}", configurationFilePath); System.exit(1); } properties = new Properties(fileProperties); properties.putAll(System.getProperties()); } else { properties = System.getProperties(); } final String logFile = properties.getProperty("net.za.slyfox.dyn53.logFile"); if(logFile != null) { final LoggerContext context = (LoggerContext)LoggerFactory.getILoggerFactory(); context.putProperty("logFile", logFile); final ch.qos.logback.classic.Logger rootLogger = context.getLogger(Logger.ROOT_LOGGER_NAME); final JoranConfigurator configurator = new JoranConfigurator(); configurator.setContext(context); try { configurator.doConfigure(Dyn53.class.getResourceAsStream("/logback-file.xml")); } catch(JoranException ignored) { } final Appender<ILoggingEvent> stdoutAppender = rootLogger.getAppender("stdout"); rootLogger.detachAppender(stdoutAppender); stdoutAppender.stop(); StatusPrinter.printInCaseOfErrorsOrWarnings(context); } Thread.setDefaultUncaughtExceptionHandler((t, e) -> LoggerFactory.getLogger(Dyn53.class).error("Thread {} terminated due to uncaught exception", t.getName(), e)); final Logger logger = LoggerFactory.getLogger(Dyn53.class); logger.info("Initializing Dyn53 application"); final Set<Module> modules = new HashSet<>(); modules.add(new ExternalIpModule()); modules.add(new SystemModule()); final String hostedZoneId = properties.getProperty("net.za.slyfox.dyn53.route53.hostedZoneId"); if(hostedZoneId == null) throw new IllegalArgumentException("Hosted zone ID missing"); final String resourceRecordSetName = properties.getProperty( "net.za.slyfox.dyn53.route53.resourceRecordSetName"); if(resourceRecordSetName == null) throw new IllegalArgumentException("Resource record set name missing"); final Long resourceRecordSetTtl = Long.valueOf(properties.getProperty("net.za.slyfox.dyn53.route53.resourceRecordSetTtl", "300")); modules.add(new Route53Module(hostedZoneId, resourceRecordSetName, resourceRecordSetTtl)); final String pidFile = properties.getProperty("net.za.slyfox.dyn53.daemon.pidFile"); if(pidFile != null) modules.add(new DaemonModule(pidFile)); final boolean alwaysUpdate = Boolean.valueOf(properties.getProperty( "net.za.slyfox.dyn53.alwaysUpdate", "false")); modules.add(alwaysUpdate ? new UnconditionalUpdateModule() : new StatefulUpdateModule()); final Injector injector = Guice.createInjector(Stage.PRODUCTION, modules); try { injector.getInstance(Dyn53.class).run(); } catch(RuntimeException e) { logger.error("Application terminated with error", e); } } @Override public void run() { logger.info("Starting Dyn53 application"); if(lifecycleObjects.isEmpty()) throw new IllegalStateException("No lifecycle objects registered"); lifecycleObjects.forEach(lifecycle -> { logger.debug("Starting {}", lifecycle); lifecycle.start(); logger.debug("Registering shutdown hook for {}", lifecycle); runtime.addShutdownHook(new Thread(lifecycle::stop, getShutdownThreadName(lifecycle))); }); } private static String getShutdownThreadName(Lifecycle lifecycle) { final Named nameAnnotation = lifecycle.getClass().getAnnotation(Named.class); return "shutdown-" + ((nameAnnotation != null) ? nameAnnotation.value() : shutdownThreadCounter.getAndIncrement()); } }